#!/bin/sh
. /etc/utils.sh

trap ": >/tmp/run/firewall.init" EXIT

register_experiment NoSIPConntrack
register_experiment McDoleCast

flush()
{
  for t in filter mangle nat; do
    ip46tables -t $t -F
    ip46tables -t $t -X
  done
}

if ! is-network-box; then
  # no iptables needed on non-network boxes
  exit 0
fi

case "$1" in
  start|restart|reload)
    if ! is-windcharger; then
      modprobe -a \
          iptable_filter \
          iptable_nat \
          ipt_tcp \
          ipt_udp \
          ipt_conntrack \
          ipt_pkttype \
          ipt_limit \
          ipt_LOG \
          ipt_REJECT \
          ipt_MASQUERADE \
          \
          nf_nat_ftp \
          nf_nat_tftp \
          nf_nat_pptp \
          nf_nat_irc \
          nf_nat_proto_gre \
          nf_nat_h323 \
          nf_nat_rtsp \
          \
          nf_conntrack_ftp \
          nf_conntrack_tftp \
          nf_conntrack_pptp \
          nf_conntrack_irc \
          nf_conntrack_proto_gre \
          nf_conntrack_h323 \
          nf_conntrack_rtsp \
          \
          ip6table_filter \
          ip6t_tcp \
          ip6t_udp \
          nf_conntrack_ipv6 \
          ip6t_pkttype \
          ip6t_limit \
          ip6t_LOG \
          ip6t_REJECT
      if ! experiment NoSIPConntrack; then
        modprobe -a \
            nf_nat_sip \
            nf_conntrack_sip
      fi
    fi

    # Flush all existing rules when starting or reloading.
    flush

    if is-ptp; then
      ip46tables -P INPUT DROP
      ip46tables -P OUTPUT ACCEPT
      ip46tables -P FORWARD DROP

      # Accept established and related connections.
      ip46tables -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT

      # ping and path MTU discovery (PMTUD)
      iptables -A INPUT -p icmp --icmp-type 8/0 -j ACCEPT
      iptables -A INPUT -p icmp --icmp-type 3/4 -j ACCEPT
      iptables -A INPUT -p icmp --icmp-type 11/0 -j ACCEPT
      ip6tables -A INPUT -p icmpv6 --icmpv6-type echo-request -j ACCEPT

      if [ -e /tmp/DEBUG ]; then
        ip46tables -A INPUT -p tcp --dport 8080 -j ACCEPT
      fi

      # adding a new chain "vlan-input", which will be updated later when
      # more interfaces are created in S40Network
      ip46tables -N vlan-input
      ip46tables -A INPUT -j vlan-input

      if [ -e /sys/class/net/craft0 ]; then
        ip46tables -A INPUT -i craft0 -p tcp --dport 22 -j ACCEPT
        ip46tables -A INPUT -i craft0 -p tcp --dport 80 -j ACCEPT
        ip46tables -A INPUT -i craft0 -p tcp --dport 443 -j ACCEPT
        # Reject everything else that comes in on the craft port. Any new rules
        # must be added above the REJECT.
        ip46tables -A INPUT -i craft0 -j REJECT
      fi

      # multicast (fiber TV, EAS)
      ip46tables -A INPUT -m pkttype --pkt-type multicast -j ACCEPT

      # Allow all incoming connections to the loopback interface.
      ip46tables -A INPUT -i lo -j ACCEPT

      # cwmpd ACS kick
      ip46tables -A INPUT -p tcp --dport 7547 -j ACCEPT    # cwmpd

      # DHCPv4 client
      iptables -A INPUT -p udp --sport 67:68 --dport 67:68 -j ACCEPT
      # DHCP6 client
      ip6tables -A INPUT -p udp --sport 547 --dport 546 -j ACCEPT

      # TODO(cgibson): Revisit allowing SSH access on all but the craft port
      # pre-launch and also the SSH keying strategy: http://b/27072881
      ip46tables -A INPUT -p tcp --dport 22 -j ACCEPT

      # IPv6 addressing
      ip6tables -A INPUT -p icmpv6 --icmpv6-type router-advertisement -j ACCEPT
      ip6tables -A INPUT -p icmpv6 --icmpv6-type router-solicitation -j ACCEPT
      ip6tables -A INPUT -p icmpv6 --icmpv6-type neighbour-advertisement -j ACCEPT
      ip6tables -A INPUT -p icmpv6 --icmpv6-type neighbour-solicitation -j ACCEPT

      # Log packets which weren't explicitly accepted by above rules
      ip46tables -N log
      ip46tables -A log -m pkttype -m limit \
          --pkt-type unicast \
          --limit 1/second \
          -j LOG --log-prefix "FW: "
      ip46tables -A INPUT -j log
    fi

    if [ -e /sys/class/net/wan0 ]; then
      # Do these first to reduce race conditions
      if [ -e /tmp/NFS ]; then
        # Workaround because we can't have a temporary network dropout
        # when NFS booted.
        ip46tables -P INPUT ACCEPT
      else
        ip46tables -P INPUT DROP
      fi
      ip46tables -P FORWARD DROP

      # captive-portal-filter and captive-portal-input contain rules that should
      # always be applied to the br1 (captive portal) interface, regardless of
      # what services we're trying to provide with it.
      ip46tables -N captive-portal-guests
      ip46tables -N captive-portal-filter
      ip46tables -N captive-portal-ntp
      ip46tables -N captive-portal-input
      ip46tables -N acs-captive-portal-input
      ip46tables -N sniproxy-input
      ip46tables -N acs-captive-portal-filter
      ip46tables -N acsrules-filter-forward
      ip46tables -N MINIUPNPD
      ip46tables -N wan-input
      ip46tables -N wan-forward
      ip46tables -N log

      ip46tables -t nat -N captive-portal-guests-nat
      ip46tables -t nat -N acs-captive-portal-nat
      iptables -t nat -N acsrules-nat-prerouting
      iptables -t nat -N acsrules-nat-postrouting
      ip46tables -t nat -N sniproxy-nat
      iptables -t nat -N MINIUPNPD
      ip46tables -t nat -N wan-nat

      for ifc in "wan0+" "frob+"; do
        ip46tables -A INPUT -i "$ifc" -j wan-input
        ip46tables -A FORWARD -i "$ifc" -j wan-forward
        ip46tables -t nat -A PREROUTING -i "$ifc" -j wan-nat

        ip46tables -A FORWARD -i br0 -o "$ifc" -j ACCEPT
      done

      ip46tables -A FORWARD -i br1 -o wan0+ -j captive-portal-guests
      ip46tables -A FORWARD -i br1 -o wan0+ -j captive-portal-filter
      ip46tables -A FORWARD -i br1 -o wan0+ -j captive-portal-ntp

      for ifc in lo br0; do
        ip46tables -A INPUT -i "$ifc" -j ACCEPT
      done

      ip46tables -A INPUT -i br1 -j captive-portal-input
      ip46tables -A INPUT -i br1 -j acs-captive-portal-input
      ip46tables -A INPUT -i br1 -j sniproxy-input

      # multicast (fiber TV, EAS)
      ip46tables -A wan-input -m pkttype --pkt-type multicast -j ACCEPT
      ip46tables -A wan-forward -m pkttype --pkt-type multicast -j ACCEPT
      ip46tables -t nat -A wan-nat -m pkttype --pkt-type multicast -j ACCEPT

      # Forwarding
      ip46tables -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
      ip46tables -A FORWARD -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT

      # igmp group membership control
      ip46tables -A wan-input -p igmp -j ACCEPT

      # ping and PMTUD
      iptables -A INPUT -p icmp --icmp-type 8/0 -j ACCEPT
      iptables -A INPUT -p icmp --icmp-type 3/4 -j ACCEPT
      iptables -A INPUT -p icmp --icmp-type 11/0 -j ACCEPT
      ip6tables -A INPUT -p icmpv6 --icmpv6-type echo-request -j ACCEPT
      # RFC4890 says RFC4380 requires forwarding ipv6 ping requests
      ip6tables -A wan-forward -p icmpv6 --icmpv6-type echo-request -j ACCEPT

      # Incoming connections
      # TODO(apenwarr): Whitelist ports here too for added safety.
      if [ -e /tmp/NFS ]; then
        # Workaround because netfilter conntracking loaded *after* the NFS
        # session was established.
        ip46tables -A wan-input -p tcp --sport 2049 -j ACCEPT
      fi

      # Outgoing connections
      # TODO(apenwarr): Add a whitelist on OUTPUT for -o wan0.
      # In particular we should have one for samba ports.

      # TODO(apenwarr): Remove this when we have real port forwarding controls.
      if [ -e /tmp/DEBUG ]; then
        ip46tables -A wan-input -p tcp --dport 22 -j ACCEPT    # ssh
        ip46tables -A wan-forward -p tcp --dport 22 -j ACCEPT  # ssh for lan clients
        ip46tables -A wan-input -p tcp --dport 5001 -j ACCEPT  # iperf
        ip46tables -A wan-input -p udp --dport 5001 -j ACCEPT  # iperf
        ip46tables -A wan-input -p udp --dport 4948 -j ACCEPT  # isoping
      fi

      # Open incoming ports for McDoleCast
      if experiment McDoleCast; then
        ip46tables -A wan-input -p tcp --dport 31098 -j ACCEPT  # sagesrv
        ip46tables -A wan-input -p tcp --dport 32825 -j ACCEPT  # marjoram
      fi
      # cwmpd ACS kick
      ip46tables -A wan-input -p tcp --dport 7547 -j ACCEPT    # cwmpd
      ip6tables -A wan-forward -p tcp --dport 7547 -j ACCEPT  # cwmpd
      ip46tables -t nat -A wan-nat -p tcp --dport 7547 -j ACCEPT  # cwmpd

      if [ -e /tmp/factory_status ]; then
        ip46tables -A wan-input -p tcp --dport 8883 -j ACCEPT  # factory web server
      fi

      # active FTP out
      ip46tables -A FORWARD -m conntrack --ctstate RELATED \
          -m helper --helper ftp -o wan0+ -p tcp --dport 1024: -j ACCEPT
      ip46tables -A wan-forward -m conntrack --ctstate RELATED \
          -m helper --helper ftp -p tcp --dport 1024: -j ACCEPT

      # add ACS-configured things
      ip46tables -A FORWARD -i br1 -j acs-captive-portal-filter
      ip46tables -A FORWARD -i wan0+ -j acsrules-filter-forward
      ip46tables -t nat -A PREROUTING -i br1 -j captive-portal-guests-nat
      ip46tables -t nat -A PREROUTING -i br1 -j acs-captive-portal-nat
      iptables -t nat -A PREROUTING -i wan0+ -j acsrules-nat-prerouting
      iptables -t nat -A POSTROUTING -o wan0+ -j acsrules-nat-postrouting

      # these are managed by sniproxy, if running
      ip46tables -t nat -A PREROUTING -j sniproxy-nat -i br1

      # these are managed by miniupnpd, if running
      ip46tables -A FORWARD -i wan0+ ! -o wan0+ -j MINIUPNPD
      iptables -t nat -A PREROUTING -i wan0+ -j MINIUPNPD

      # nat
      ip46tables -t nat -A POSTROUTING -o wan0+ -j MASQUERADE

      # ipv6 addressing
      ip6tables -A INPUT -p icmpv6 --icmpv6-type router-advertisement -j ACCEPT
      ip6tables -A INPUT -p icmpv6 --icmpv6-type router-solicitation -j ACCEPT
      ip6tables -A INPUT -p icmpv6 --icmpv6-type neighbour-advertisement -j ACCEPT
      ip6tables -A INPUT -p icmpv6 --icmpv6-type neighbour-solicitation -j ACCEPT
      # DHCP6 client
      ip6tables -A INPUT -p udp --sport 547 --dport 546 -j ACCEPT

      # Log packets which weren't explicitly accepted by above rules
      ip46tables -A log -m pkttype -m limit \
          --pkt-type unicast \
          --limit 1/second \
          -j LOG --log-prefix "FW: "
      ip46tables -A INPUT -i wan0+ -j log
      ip46tables -A FORWARD -i wan0+ -j log

      # Set the WAN 802.1q pbits
      iptables -t mangle -A OUTPUT -o wan0.2 -p all -j CLASSIFY --set-class 0:2
      iptables -t mangle -A OUTPUT -o wan0.2 -p igmp -j CLASSIFY --set-class 0:6
      iptables -t mangle -A OUTPUT -o wan0.2 -p icmp -j CLASSIFY --set-class 0:6
      ip6tables -t mangle -A OUTPUT -o wan0.2 -p icmpv6 -j CLASSIFY --set-class 0:6
      # DHCP and DHCPv6
      iptables -t mangle -A OUTPUT -o wan0.2 -p udp --sport 68 --dport 67 -j CLASSIFY --set-class 0:6
      ip6tables -t mangle -A OUTPUT -o wan0.2 -p udp --sport 547 --dport 546 -j CLASSIFY --set-class 0:6

      # Default DROP policy above is safe, but REJECT is friendlier.
      # But you can't use REJECT as a policy, so add a catchall rule at the
      # bottom.
      ip46tables -A INPUT -j REJECT
      ip46tables -A FORWARD -j REJECT

      # don't add any rules after the REJECTs above

      # parse acs state into acsrules chains
      update-acs-iptables

      # Use iptables to mark packets to set the VLAN p-bits
      # Bit(s)  Definitions
      # 0:4     class/priority of orig [and reply] connection.
      # 5:7     VLAN P bits for orig [and reply] connection (if bit 15 = 1)
      #
      # 8       DSCP control bit
      # 0:      DSCP values will remain unchanged
      # 1:      do DSCP marking for orig [and reply] connection
      #
      # 9:14    DSCP value: DSCP value for orig [and reply] connection (if bit 8 # = 1)
      #
      # 15      if 0, copy VLAN P bits from ingress packet;
      #         if 1, set VLAN P bits for orig and reply connection
      #
      # 16:20   class/priority of reply connection
      # 21:23   VLAN P bits for reply connection (if bit 15 = 1)
      #
      # 24      DSCP control bit
      #         0:      DSCP values will remain unchanged
      #         1:      do DSCP marking for reply connection;
      #
      # 25:30   DSCP value for reply connection (if bit 24 = 1)
      # 31      0: use bits 0-15 for reply connection
      #         1: use bits 16-30 for reply connection
      ip46tables -A POSTROUTING -t mangle -o wan0.2 -j CONNMARK --set-mark 0x8040
    fi
    exit 0
    ;;
  stop)
    if [ -e /proc/sys/net/netfilter -a -e /sys/class/net/wan0 ]; then
      flush
      ip46tables -P INPUT ACCEPT
      ip46tables -P FORWARD ACCEPT
    fi
    ;;
  *)
    echo "Usage: $0 {start|stop|restart}"
    exit 1
esac
